/*
 * Created on 24-Apr-2003
 * Last updated 25-Mar-2005
 * 
 * * Software License
 *
 * Copyright (c) 2003 David Bridgewater.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        David Bridgewater (http://www.jbridge.co.uk/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL DAVID BRIDGEWATER
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 
 */
package uk.co.jbridge.httpclient;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;

import java.util.*;

import javax.swing.*;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;

/**
 * @author David Bridgewater
 *
 * The UserInterface class sets up the user interface for the Bridgewater Browser.
 * The different areas of the interface (address line, method choice line, 
 * execute button line etc.) are attached to different Java Swing panels (JPanels), 
 * and set up in separate methods.
 *  
 */
public class UserInterface extends JPanel {

	private JButton execHttpRequest;
	private JTextField urlText, putFileChoice, postData;
	private JComboBox httpMethodChoice;
	private JTable requestTable, responseTable;
	private JTextArea responseBody;
	private File fileForPut;
	private JLabel responseCodeText, methodComment;
	private int responseCode;
	// If the safety catch is set on (true), then the unsafe methods
	// POST, DELETE and PUT will only be allowed against a "localhost" web server,
	// i.e. a web server running on the same machine as the client.
	private boolean safetyCatch = true;

	/** The constructor coordinates the set up of the individual panels */
	public UserInterface() {
		super();

		JPanel wholePane = new JPanel();
		wholePane.setLayout(new BoxLayout(wholePane, BoxLayout.Y_AXIS));

		JPanel p1 = setupPanelURL();
		p1.setAlignmentX(Component.LEFT_ALIGNMENT);

		JPanel p2 = setupPanelHttpMethod();
		p2.setAlignmentX(Component.LEFT_ALIGNMENT);

		JPanel p3 = setupPanelPostData();
		p3.setAlignmentX(Component.LEFT_ALIGNMENT);

		JPanel p4 = setupPanelPutFileChoice();
		p4.setAlignmentX(Component.LEFT_ALIGNMENT);

		JPanel p5 = setupPanelExecute();
		p5.setAlignmentX(Component.LEFT_ALIGNMENT);

		/*JPanel p6 = new JPanel();
		p6.setLayout(new BoxLayout(p6, BoxLayout.X_AXIS));*/
		
		JTabbedPane t1 = new JTabbedPane();
		JPanel p7 = setupPanelRequest();
		p7.setName("Request Headers");
		JPanel p8 = setupPanelResponse();
		p8.setName("Response Headers");
		/*p6.add(p7);
		p6.add(p8);
		p6.setAlignmentX(Component.LEFT_ALIGNMENT);*/

		JPanel p9 = setupPanelResponseBody();
		p9.setName("Response Body");
		
		t1.setAlignmentX(Component.LEFT_ALIGNMENT);
		t1.add(p7);
		t1.add(p8);
		t1.add(p9);
		
		wholePane.add(p1);
		wholePane.add(p2);
		wholePane.add(p3);
		wholePane.add(p4);
		wholePane.add(p5);
		wholePane.add(t1);
		/*wholePane.add(p6);
		wholePane.add(p9);*/

		add(wholePane);
	}

	/* This method sets up the panel for the address line at the top of the browser */
	protected JPanel setupPanelURL() {

		JPanel urlPane = new JPanel();
		urlPane.setLayout(new BoxLayout(urlPane, BoxLayout.X_AXIS));

		JLabel label = new JLabel("URL:");
		label.setMaximumSize(new Dimension(100, 20));
		label.setPreferredSize(label.getMaximumSize());
		label.setHorizontalAlignment(JLabel.RIGHT);

		urlText = new JTextField("http://localhost:8080/");
		urlText.setMaximumSize(new Dimension(400, 20));
		urlText.setPreferredSize(urlText.getMaximumSize());

		urlPane.add(label);
		urlPane.add(Box.createRigidArea(new Dimension(10, 20)));
		urlPane.add(urlText);
		urlPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

		return urlPane;
	}
	
	/* This method sets up the panel for choice of HTTP method */
	protected JPanel setupPanelHttpMethod() {

		JPanel httpMethodPane = new JPanel();
		httpMethodPane.setLayout(
			new BoxLayout(httpMethodPane, BoxLayout.X_AXIS));

		JLabel label = new JLabel("Http Method:");
		label.setMaximumSize(new Dimension(100, 20));
		label.setPreferredSize(label.getMaximumSize());
		label.setHorizontalAlignment(JLabel.RIGHT);

		httpMethodChoice = new JComboBox();
		httpMethodChoice.addItem("GET");
		httpMethodChoice.addItem("HEAD");
		httpMethodChoice.addItem("POST");
		httpMethodChoice.addItem("PUT");
		httpMethodChoice.addItem("OPTIONS");
		httpMethodChoice.addItem("TRACE");
		httpMethodChoice.addItem("DELETE");
		httpMethodChoice.setMaximumSize(new Dimension(100, 20));
		httpMethodChoice.setPreferredSize(httpMethodChoice.getMaximumSize());
		httpMethodChoice.addActionListener(new MethodChoiceAction());

		// The default method is 'GET', for which 'Safe' is the appropriate method description.
		methodComment = new JLabel("Safe");

		httpMethodPane.add(label);
		httpMethodPane.add(Box.createRigidArea(new Dimension(10, 0)));
		httpMethodPane.add(httpMethodChoice);
		httpMethodPane.add(Box.createRigidArea(new Dimension(10, 0)));
		httpMethodPane.add(methodComment);
		httpMethodPane.setBorder(
			BorderFactory.createEmptyBorder(10, 10, 10, 10));

		return httpMethodPane;
	}
	
	/* This method sets up the panel to allow entry of text for POSTing */
	protected JPanel setupPanelPostData() {

		JPanel postDataPane = new JPanel();
		postDataPane.setLayout(new BoxLayout(postDataPane, BoxLayout.X_AXIS));

		JLabel label = new JLabel("Text to POST:");
		label.setMaximumSize(new Dimension(100, 20));
		label.setPreferredSize(label.getMaximumSize());
		label.setHorizontalAlignment(JLabel.RIGHT);

		postData = new JTextField("Type any text you like here");
		postData.setMaximumSize(new Dimension(400, 20));
		postData.setPreferredSize(postData.getMaximumSize());

		postDataPane.add(label);
		postDataPane.add(Box.createRigidArea(new Dimension(10, 0)));
		postDataPane.add(postData);
		postDataPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

		return postDataPane;
	}

	/* This method sets up the panel to choose a file to PUT on the target web server */
	protected JPanel setupPanelPutFileChoice() {

		JPanel fileChoicePane = new JPanel();
		fileChoicePane.setLayout(
			new BoxLayout(fileChoicePane, BoxLayout.X_AXIS));

		JLabel label = new JLabel("File to PUT:");
		label.setMaximumSize(new Dimension(100, 20));
		label.setPreferredSize(label.getMaximumSize());
		label.setHorizontalAlignment(JLabel.RIGHT);

		putFileChoice = new JTextField("C:\\My Documents\\MyFile.txt");
		putFileChoice.setEditable(false);
		putFileChoice.setMaximumSize(new Dimension(400, 20));
		putFileChoice.setPreferredSize(putFileChoice.getMaximumSize());

		JButton browseFiles = new JButton("Browse Files");
		browseFiles.addActionListener(new FileChoiceAction());
		browseFiles.setMaximumSize(browseFiles.getMinimumSize());
		browseFiles.setPreferredSize(browseFiles.getMinimumSize());

		fileChoicePane.add(label);
		fileChoicePane.add(Box.createRigidArea(new Dimension(10, 0)));
		fileChoicePane.add(putFileChoice);
		fileChoicePane.add(Box.createRigidArea(new Dimension(10, 0)));
		fileChoicePane.add(browseFiles);
		fileChoicePane.setBorder(
			BorderFactory.createEmptyBorder(10, 10, 10, 10));

		return fileChoicePane;
	}

	/* This method sets up the panel for button to fire the HTTP request */
	protected JPanel setupPanelExecute() {

		JPanel executePane = new JPanel();
		executePane.setLayout(new BoxLayout(executePane, BoxLayout.X_AXIS));

		execHttpRequest = new JButton("Execute HTTP Request");
		execHttpRequest.addActionListener(new HttpRequestAction());
		execHttpRequest.setMaximumSize(new Dimension(200, 20));
		execHttpRequest.setPreferredSize(execHttpRequest.getMaximumSize());
		execHttpRequest.setBackground(Color.LIGHT_GRAY);
		//execHttpRequest.set

		responseCodeText = new JLabel("");

		executePane.add(Box.createRigidArea(new Dimension(110, 20)));
		executePane.add(execHttpRequest);
		executePane.add(Box.createRigidArea(new Dimension(10, 20)));
		executePane.add(responseCodeText);
		executePane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

		return executePane;
	}

	/* This method sets up the panel to display request header fields */
	protected JPanel setupPanelRequest() {

		JPanel panelRequest = new JPanel();
		panelRequest.setLayout(new BoxLayout(panelRequest, BoxLayout.Y_AXIS));

		JLabel label = new JLabel("Request Information");

		requestTable = new JTable(new InfoModel());
		JScrollPane scrollPane = new JScrollPane(requestTable);
		requestTable.setPreferredScrollableViewportSize(
			new Dimension(300, 300));

		panelRequest.add(label);
		panelRequest.add(scrollPane);

		return panelRequest;
	}

	/* This method sets up the panel to display response header fields */
	protected JPanel setupPanelResponse() {

		JPanel panelResponse = new JPanel();
		panelResponse.setLayout(new BoxLayout(panelResponse, BoxLayout.Y_AXIS));

		JLabel label = new JLabel("Response Information");
	
		responseTable = new JTable(new InfoModel());
		//setResponseColumns(responseTable);
		JScrollPane scrollPane = new JScrollPane(responseTable);
		responseTable.setPreferredScrollableViewportSize(
		  new Dimension(500, 300));

		panelResponse.add(label);
		panelResponse.add(scrollPane);

		return panelResponse;
	}

	/* This method is a helper method to fix appropriate column widths
	 * for the response information table */
	protected void setResponseColumns(JTable responseTable) {
		
		int colCount = responseTable.getColumnCount();
		TableColumn tc = null;
		for (int i = 0; i < colCount; i++) {
			switch (i) {
				case 0 :
					// Key
					tc = responseTable.getColumnModel().getColumn(i);
					tc.setPreferredWidth(100);
					tc.setMaxWidth(tc.getPreferredWidth());
					break;
				case 1 :
					// What kind of field
					tc = responseTable.getColumnModel().getColumn(i);
					tc.setPreferredWidth(50);
					tc.setMaxWidth(tc.getPreferredWidth());
					break;
				case 2 :
					// Value
					tc = responseTable.getColumnModel().getColumn(i);
		
					tc.setPreferredWidth(350);
					tc.setMaxWidth(tc.getPreferredWidth());
					break;
			}
		
		}
	}

	/* This method sets up the panel displaying the body of the requested resource */
	protected JPanel setupPanelResponseBody() {

		JPanel panelResponseBody = new JPanel();
		panelResponseBody.setLayout(
			new BoxLayout(panelResponseBody, BoxLayout.Y_AXIS));

		JLabel label = new JLabel("Response Body");

		responseBody = new JTextArea();
		JScrollPane scrollPane = new JScrollPane(responseBody);
		scrollPane.setPreferredSize(new Dimension(800, 350));

		panelResponseBody.add(label);
		panelResponseBody.add(scrollPane);

		return panelResponseBody;
	}

	/* A helper method for the set up of the request and response information 
	 * panels: associating the information with a TableModel (InfoModel) 
	 * which underpins the displayed JTable. */
	protected void loadInfo(HttpClient hc, Map info, JTable table) {

		Set s = info.keySet();
		String[] keys = new String[info.size()];
		String[] types = new String[info.size()];
		String[] values = new String[info.size()];
		Iterator it = s.iterator();

		for (int i = 0; i < info.size(); i++) {
			keys[i] = "" + it.next();
			types[i] = hc.getTypeForHeaderKey(keys[i]);
			values[i] = "" + info.get(keys[i]);
		}

		InfoModel im = new InfoModel(keys, types, values);
		table.setModel(im);
		
	}

	/* A helper method for the set up of the response body text */
	protected void loadBody(ArrayList body, JTextArea bodyText) {

		bodyText.setText("");
		bodyText.setRows(20);
		bodyText.setColumns(80);
		bodyText.setLineWrap(true);
		Iterator it = body.iterator();
		while (it.hasNext()) {
			bodyText.append(it.next() + "\n");
		}
	}

	/* A helper method: given the name of a method, is it safe or not? */
	protected boolean isMethodSafe(String method) {
		if ("POST;PUT;DELETE".indexOf(method) > -1) {
			return false;
		}
		return true;
	}

	/* The main method, which launches a JFrame to contain the whole 
	 * gamut of panels in the UserInterface object. */
	public static void main(String[] args) {
		UserInterface ui = new UserInterface();

		JFrame frame = new JFrame("HTTP Client User Interface");
		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});
		frame.getContentPane().add("Center", ui);
		frame.getRootPane().setDefaultButton(ui.getExecHttpRequest());
		frame.setSize(900, 920);
		frame.setLocation(100, 50);
		// frame.pack();
		frame.setVisible(true);
	}

	/** The HttpRequestAction class processes the button press to 
	 *  execute the HTTP request */	
	class HttpRequestAction extends AbstractAction {

		public void actionPerformed(ActionEvent e) {
			// HttpClient is the core class that handles the HTTP communication
			HttpClient hc = new HttpClient();
			// HttpClient needs to know some information from the appropriate
			// user interface components.
			String url = urlText.getText();
			hc.setTheURL(url);
			String method = (String) httpMethodChoice.getSelectedItem();
			hc.setTheHttpMethod(method);
			hc.setDataForPost(postData.getText());
			hc.setFileForPut(fileForPut);
			// Fire the request, pending the safety check...
			// - am I checking at all?  (safety catch on)
			// - is this a request of a public web server?
			// - is the method unsafe, i.e. POST, PUT or DELETE?
			// If all these are true, the request is blocked and a rude message sent.
			if (isSafetyCatch() && !isLocalRequest(url) && !isMethodSafe(method)) {
				responseCodeText.setText("!! Unsafe request method disallowed (except for localhost requests) !!");
				return;
			}
			hc.executeHttpRequest();
			// Load the appropriate user interface components from the 
			// information that should now be available within the 
			// HttpClient object.
			loadInfo(hc, hc.getRequestInfo(), requestTable);
			loadInfo(hc, hc.getResponseInfo(), responseTable);
			//setResponseColumns(responseTable);
			loadBody(hc.getResponseBody(), responseBody);
			responseCode = hc.getResponseCode();
			responseCodeText.setText(
				responseCode + ": " + hc.getTextForResponseCode(responseCode));

		}
		
		/* The isLocalRequest() helper method determines if the URL provided is for a web server running
		 * on this machine
		 */
		protected boolean isLocalRequest(String url) {
			if (url.toLowerCase().startsWith("http://localhost")) {
				return true;
			}
			return false;
		}
	}

	/** The FileChoiceAction helper class processes the button press
	 * requesting to browse files in the file system (for a file to PUT).
	 * */
	class FileChoiceAction extends AbstractAction {

		public void actionPerformed(ActionEvent e) {
			JFileChooser fc = new JFileChooser();
			
			int result = fc.showOpenDialog(UserInterface.this);
			if (result == JFileChooser.APPROVE_OPTION) {
				fileForPut = fc.getSelectedFile();
				putFileChoice.setText(fileForPut.getAbsolutePath());
			} else {
				fileForPut = null;
				putFileChoice.setText("");
			}
		}
	}

	/** The MethodChoiceAction helper class processes changes to the 
	 * chosen HTTP method (from the drop down JComboBox).
	 * Descriptions and colours are then changed as appropriate.
	 */
	class MethodChoiceAction extends AbstractAction {

		public void actionPerformed(ActionEvent e) {
			JComboBox jc = (JComboBox) e.getSource();
			String method = (String) jc.getSelectedItem();
			if (isMethodSafe(method)) {
				execHttpRequest.setBackground(Color.LIGHT_GRAY);
				methodComment.setText("Safe");
				methodComment.setForeground(Color.BLACK);
			} else {
				execHttpRequest.setBackground(Color.RED);
				methodComment.setText("UNSAFE");
				methodComment.setForeground(Color.RED);
			}
		}
	}

	/** The InfoModel helper class underpins the request and response
	 * information JTables.
	 * The InfoModel instance is loaded with data in a way that the 
	 * JTable finds convenient to interpret and render.
	 */
	class InfoModel extends AbstractTableModel {
		protected String[] keys = { "" };
		protected String[] types = { "" };
		protected String[] values = { "" };

		protected String[] columnNames = new String[] { "Key", "Type", "Value" };
		protected Properties typeDescriptions = new Properties();

		public InfoModel(String[] keys, String[] types, String[] values) {
			this.keys = keys;
			this.types = types;
			this.values = values;

		}

		public InfoModel() {
			// Default constructor required
		}

		public int getColumnCount() {
			return 3;
		}
		public int getRowCount() {
			return keys.length;
		}
		public String getColumnName(int col) {
			return columnNames[col];
		}
		public Class getColumnClass(int col) {
			return String.class;
		}

		public Object getValueAt(int row, int col) {
			switch (col) {
				case 0 :
					return keys[row];
				case 1 :
					return types[row];
				case 2 : 
					return values[row];
				default :
					return null;
			}
		}

	}

	/* Getters & Setters follow... */
	protected void setResponseCode(int i) {
		responseCode = i;
	}
	public JButton getExecHttpRequest() {
		return execHttpRequest;
	}
	public boolean isSafetyCatch() {
		return safetyCatch;
	}

	

}
